import { ShaderMaterial, IShaderMaterialOptions } from '@babylonjs/core/Materials/shaderMaterial';
import { Texture } from '@babylonjs/core/Materials/Textures/texture';
import { Color3 } from '@babylonjs/core/Maths/math.color';
import { Scene } from '@babylonjs/core/scene';

/**
 * 纹理模糊材质参数对象
 * @param texture 纹理
 * @param blur 水平模糊
 * @param alphaColor 透明通道的替换颜色
 * @param fastMode 快速模式，模糊方向减少为4个，速度更快，效果更差
 * @param useRandom 模糊范围内使用随机值
 * @param size 纹理缩放
 * @param uOffset 纹理水平偏移
 * @param vOffset 纹理垂直偏移
 * @param angle 纹理旋转
 * @param invertU 水平翻转纹理
 * @param invertV 垂直翻转纹理
 */
interface ITextureBlurMaterialOptions extends IShaderMaterialOptions {
    texture?: Texture,
    blur?: number,
    alphaColor?: Color3,
    fastMode?: boolean,
    useRandom?: boolean,
    size?: number,
    uOffset?: number,
    vOffset?: number,
    angle?: number,
    invertU?: boolean,
    invertV?: boolean,
}


/**
 * 纹理模糊材质
 * @param name 名称
 * @param scene 所在场景
 * @param options 参数对象
 */
class TextureBlurMaterial extends ShaderMaterial {

    private _texture!: Texture;
    private _blur!: number;
    private _useAlpha!: boolean;
    private _alphaColor!: Color3;
    private _fastMode!: boolean;
    private _useRandom!: boolean;
    private _size!: number;
    private _uOffset!: number;
    private _vOffset!: number;
    private _angle!: number;
    private _invertU!: boolean;
    private _invertV!: boolean;

    constructor(name: string, scene: Scene, options?: Partial<ITextureBlurMaterialOptions>) {
        super(name, scene, '', options);
        this.initialize(options);
    }

    /**
     * 纹理
     */
    public set texture(texture: Texture) {
        this._texture = texture;
        this.setTexture('textureColor', texture);
    }
    public get texture(): Texture {
        return this._texture;
    }

    /**
     * 模糊
     */
    public set blur(blur: number) {
        this._blur = blur;
        this.setFloat('blur', blur);
    }
    public get blur(): number {
        return this._blur;
    }

    /**
     * 透明通道替换颜色
     */
    public set alphaColor(color: Color3) {
        this._alphaColor.copyFrom(color);
        this.setColor3('alphaColor', color);
    }
    public get alphaColor(): Color3 {
        return this._alphaColor;
    }

    /**
     * 快速模式
     */
    public set fastMode(fastMode: boolean) {
        this._fastMode = fastMode;
        this.setUInt('fastMode', fastMode ? 1 : 0);
    }
    public get fastMode(): boolean {
        return this._fastMode;
    }

    /**
     * 模糊范围内使用随机值
     */
    public set useRandom(useRandom: boolean) {
        this._useRandom = useRandom;
        this.setFloat('useRandom', useRandom ? 1 : 0);
    }
    public get useRandom(): boolean {
        return this._useRandom;
    }

    /**
     * 纹理缩放
     */
    public set size(size: number) {
        this._size = size;
        this.setFloat('size', size);
    }
    public get size(): number {
        return this._size;
    }

    /**
     * 水平偏移
     */
    public set uOffset(offset: number) {
        this._uOffset = offset;
        this.setFloat('uOffset', offset);
    }
    public get uOffset(): number {
        return this._uOffset;
    }

    /**
     * 垂直偏移
     */
    public set vOffset(offset: number) {
        this._vOffset = offset;
        this.setFloat('vOffset', offset);
    }
    public get vOffset(): number {
        return this._vOffset;
    }

    /**
     * 旋转角度
     */
    public set angle(angle: number) {
        this._angle = angle;
        this.setFloat('angle', angle);
    }
    public get angle(): number {
        return this._angle;
    }

    /**
     * 水平翻转
     */
    public set invertU(invert: boolean) {
        this._invertU = invert;
        this.setInt('invertU', invert ? 1 : 0);
    }
    public get invertU(): boolean {
        return this._invertU;
    }

    /**
     * 垂直翻转
     */
    public set invertV(invert: boolean) {
        this._invertV = invert;
        this.setInt('invertV', invert ? 1 : 0);
    }
    public get invertV(): boolean {
        return this._invertV;
    }

    /**
     * 初始化
     * @param options 参数对象
     */
    private initialize(options?: Partial<ITextureBlurMaterialOptions>): void {
        this.initializeVariable(options);
        this.initializeShader();
        this.initializeUniforms();
    }

    /**
     * 初始化变量值
     * @param options 参数对象
     */
    protected initializeVariable(options?: Partial<ITextureBlurMaterialOptions>): void {
        const op = options || {};
        this._texture = op.texture || new Texture('', this.getScene());
        this._blur = op.blur || 0;
        this._useAlpha = !!(op.needAlphaBlending || op.needAlphaTesting);
        this._alphaColor = op.alphaColor || Color3.FromHexString('#ffffff');
        this._fastMode = !!op.fastMode;
        this._useRandom = !!op.useRandom;
        this._size = op.size || 1;
        this._uOffset = op.uOffset || 0;
        this._vOffset = op.vOffset || 0;
        this._angle = op.angle || 0;
        this._invertU = !!op.invertU;
        this._invertV = !!op.invertV;
        this.doNotSerialize = true;
    }

    /**
     * 设置uniform值
     */
    protected initializeUniforms(): void {
        this.options.attributes = ['position', 'uv'];
        this.options.uniforms = ['worldView', 'projection',
            'uBlur', 'vBlur', 'useAlpha', 'alphaColor', 'fastMode', 'useRandom', 'size', 'uOffset', 'vOffset', 'angle', 'invertU', 'invertV'];
        this.options.samplers = ['textureColor'];

        this.setTexture('textureColor', this._texture);
        this.setFloat('blur', this._blur);
        this.setInt('useAlpha', this._useAlpha ? 1 : 0);
        this.setColor3('alphaColor', this._alphaColor);
        this.setInt('fastMode', this._fastMode ? 1 : 0);
        this.setInt('useRandom', this._useRandom ? 1 : 0);
        this.setFloat('size', this._size);
        this.setFloat('uOffset', this._uOffset);
        this.setFloat('vOffset', this._vOffset);
        this.setFloat('angle', this._angle);
        this.setInt('invertU', this._invertU ? 1 : 0);
        this.setInt('invertV', this._invertV ? 1 : 0);
    }

    /**
     * 初始化顶点着色器和片元着色器
     */
    protected initializeShader(): void {
        this.shaderPath = {
            vertexSource: `
                precision highp float;
                attribute vec3 position;
                attribute vec2 uv;
                uniform mat4 worldView;
                uniform mat4 projection;
                uniform float size;
                uniform float uOffset;
                uniform float vOffset;
                uniform float angle;
                uniform int invertU;
                uniform int invertV;
                varying vec2 vuv;

                vec2 rotate( float ang, vec2 v ){
                   float s = sin(ang);
                   float c = cos(ang);
                   return vec2( c*v.x-s*v.y, s*v.x+c*v.y );
                }

                void main(){
                    vec4 mvPosition=worldView*vec4(position,1.0);
                    float u=uv.x;
                    float v=uv.y;
                    if(invertU==1){
                        u=1.-u;
                    }
                    if(invertV==1){
                        v=1.-v;
                    }
                    vuv=vec2(u,v)*size+vec2(uOffset,vOffset);
                    if(angle!=0.){
                        vuv=rotate(angle,vuv);
                    }
                    gl_Position=projection*mvPosition;
                }
            `,
            fragmentSource: `
                precision highp float;
                uniform sampler2D textureColor; 
                uniform float blur;
                uniform int useAlpha; 
                uniform int useRandom; 
                uniform vec3 alphaColor; 
                uniform int fastMode; 
                varying vec2 vuv;

                float random(vec2 p){
                    return fract(sin(p.x*p.y)*54638.74539);
                }

                vec4 blur8(vec2 tuv,float fuzzy,sampler2D tex,float r,float ra){ 
                   vec4 c=texture2D(tex,vec2(tuv.x+fuzzy*(r+ra),tuv.y)); 
                   c+=texture2D(tex,vec2(tuv.x,tuv.y-fuzzy*(fract(r*10.)+ra))); 
                   c+=texture2D(tex,vec2(tuv.x-fuzzy*(fract(r*100.)+ra),tuv.y)); 
                   c+=texture2D(tex,vec2(tuv.x,tuv.y+fuzzy*(fract(r*1000.)+ra))); 
                   float fr=fuzzy*0.7071;
                   c+=texture2D(tex,vec2(tuv.x+fr*(fract(r*3.)+ra),tuv.y+fr*(fract(r*7.)+ra)));
                   c+=texture2D(tex,vec2(tuv.x-fr*(fract(r*30.)+ra),tuv.y+fr*(fract(r*70.)+ra)));
                   c+=texture2D(tex,vec2(tuv.x+fr*(fract(r*300.)+ra),tuv.y-fr*(fract(r*700.)+ra)));
                   c+=texture2D(tex,vec2(tuv.x-fr*(fract(r*3000.)+ra),tuv.y-fr*(fract(r*7000.)+ra))); 
                   return c*0.125;
                }

                vec4 blur4(vec2 tuv,float fuzzy,sampler2D tex,float r,float ra){ 
                   vec4 c=texture2D(tex,vec2(tuv.x+fuzzy*(r+ra),tuv.y)); 
                   c+=texture2D(tex,vec2(tuv.x,tuv.y-fuzzy*(fract(r*10.)+ra))); 
                   c+=texture2D(tex,vec2(tuv.x-fuzzy*(fract(r*100.)+ra),tuv.y)); 
                   c+=texture2D(tex,vec2(tuv.x,tuv.y+fuzzy*(fract(r*1000.)+ra))); 
                   return c*0.25;
                }

                void main(){
                    vec2 tuv=vuv;
                    vec4 col;
                    float r=0.;
                    float ra=1.;
                    if(useRandom==1){
                        r=random(tuv);
                        ra=0.;
                    }
                    if(fastMode==1){
                        col=blur4(tuv,blur,textureColor,r,ra);
                    }else{
                        col=blur8(tuv,blur,textureColor,r,ra);
                    }
                    float a=1.;
                    if(useAlpha==1){
                        a=col.a;    
                    }else{
                        col.rgb=col.rgb*col.a+alphaColor*(1.-col.a);
                    }
                    gl_FragColor=vec4(col.rgb,a);
                }
            `,
        };
    }

}


export { TextureBlurMaterial };
export type { ITextureBlurMaterialOptions };